//-----------------------------------------------------------------------------
/*

Timer Functions

Setup the hardware timers for:

* The generation of stepper motor pulses
* The generation of periodic callbacks to the cdc interface
* The generation of the G540 charge pump signal

*/
//-----------------------------------------------------------------------------

#include "stm32f4xx_hal.h"
#include "timers.h"
#include "gpio.h"

//-----------------------------------------------------------------------------

// enable the peripheral clock for the timers
static void enable_tim_clock(TIM_TypeDef *tim)
{
    if (tim == TIM2) {
        RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    } else if (tim == TIM3) {
        RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    } else if (tim == TIM4) {
        RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
    }
}

// enable timer interrupts
// set the preempt and sub priority
static void enable_tim_interrupt(TIM_TypeDef *tim, uint32_t pre, uint32_t sub)
{
    uint32_t irq;

    if (tim == TIM2) {
        irq = TIM2_IRQn;
    } else if (tim == TIM3) {
        irq = TIM3_IRQn;
    } else if (tim == TIM4) {
        irq = TIM4_IRQn;
    } else {
        return;
    }

    HAL_NVIC_SetPriority(irq, pre, sub);
    NVIC_EnableIRQ(irq);
}

//-----------------------------------------------------------------------------
// Use TIM2 to generate stepper motor timing pulses
// Output is generated by interrupts and GPIO control in the ISRs.
// channel1 - generates delay from direction change to start of step pulse
// channel2 - generates the step pulse duration
// timer period - generates the total step period

#define STEP_TIMER TIM2

static void step_timer_init(void)
{
    TIM_TypeDef* const TIMx = STEP_TIMER;

    // enable the peripheral clock
    enable_tim_clock(TIMx);

    // up counter, edge aligned mode, arr is not buffered
    TIMx->CR1 = 0;
    TIMx->CR2 = 0;
    // slave mode control register (not used)
    TIMx->SMCR = 0;
    // disable and clear interrupts (for now)
    TIMx->DIER = 0;
    TIMx->SR = 0;
    // setup the output control mode (no output, no CCR preload)
    TIMx->CCMR1 = 0;
    TIMx->CCMR2 = 0;
    // disable output
    TIMx->CCER = 0;
    // setup the counter, reload value and prescalar
    TIMx->CNT = 0;
    TIMx->PSC = ((SystemCoreClock / 2) /(TICKS_PER_MICROSECOND * 1000000)) - 1;
    TIMx->ARR = 0;
    // setup the output compare values (0 for now)
    TIMx->CCR1 = 0;
    TIMx->CCR2 = 0;
    TIMx->CCR3 = 0;
    TIMx->CCR4 = 0;
    // dma is not used
    TIMx->DCR = 0;
    TIMx->DMAR = 0;

    // Enable the interrupt - run this at high priority.
    // we don't want other interrupts altering the step timing.
    enable_tim_interrupt(TIMx, 0, 0);
}

void step_isr_enable(void)
{
    TIM_TypeDef* const TIMx = STEP_TIMER;
    uint32_t enable = TIM_DIER_UIE;

    // enable interrupts
    if (TIMx->CCR1) {
        // the dirn to step pulse delay is non-zero, enable channel 1
         enable |= TIM_DIER_CC1IE;
    }
    if (TIMx->CCR2) {
        // the step pulse is non-zero, enable channel 2
         enable |= TIM_DIER_CC2IE;
    }
    TIMx->DIER |= enable;

    // turn on the timer
    TIMx->CR1 |= TIM_CR1_CEN;
}

void step_isr_disable(void)
{
    TIM_TypeDef* const TIMx = STEP_TIMER;
    // turn off the timer
    TIMx->CR1 &= ~TIM_CR1_CEN;
    // disable interrupts (updates, channel1, channel2)
    TIMx->DIER &= ~(TIM_DIER_UIE | TIM_DIER_CC1IE | TIM_DIER_CC2IE);
}

void set_step_period(uint32_t ticks)
{
    TIM_TypeDef* const TIMx = STEP_TIMER;
    uint32_t saved = TIMx->CR1 & TIM_CR1_CEN;
    TIMx->CR1 &= ~TIM_CR1_CEN;
    TIMx->ARR = ticks;
    if (TIMx->CNT >= ticks) {
        TIMx->CNT = ticks;
    }
    TIMx->CR1 |= saved;
}

void set_step_pulse_delay(uint32_t ticks)
{
    TIM_TypeDef* const TIMx = STEP_TIMER;
    TIMx->CCR1 = ticks;
}

void set_step_pulse_time(uint32_t ticks)
{
    TIM_TypeDef* const TIMx = STEP_TIMER;
    TIMx->CCR2 = ticks;
}

void TIM2_IRQHandler(void)
{
    TIM_TypeDef* const TIMx = STEP_TIMER;

    if ((TIMx->SR & TIM_SR_CC1IF) && (TIMx->DIER & TIM_DIER_CC1IE)) {
        TIMx->SR &= ~TIM_SR_CC1IF;
        step_delay_isr();
    }

    if ((TIMx->SR & TIM_SR_CC2IF) && (TIMx->DIER & TIM_DIER_CC2IE)) {
        TIMx->SR &= ~TIM_SR_CC2IF;
        step_pulse_isr();
    }

    if ((TIMx->SR & TIM_SR_UIF) && (TIMx->DIER & TIM_DIER_UIE)) {
        TIMx->SR &= ~TIM_SR_UIF;
        step_period_isr();
    }
}

//-----------------------------------------------------------------------------
// Generate a periodic callback to the CDC interface

#ifdef USB_SERIAL

#define CDC_TIMER TIM3
#define CDC_TIMER_HZ 200
#define CDC_TIMER_PERIOD 5000

static void cdc_timer_init(void)
{
    TIM_TypeDef* const TIMx = CDC_TIMER;

    // enable the peripheral clock
    enable_tim_clock(TIMx);

    // up counter, edge aligned mode, arr is buffered
    TIMx->CR1 = TIM_CR1_ARPE;
    TIMx->CR2 = 0;
    // slave mode control register (not used)
    TIMx->SMCR = 0;
    // disable and clear interrupts
    TIMx->DIER = 0;
    TIMx->SR = 0;
    // no output/input control
    TIMx->CCMR1 = 0;
    TIMx->CCMR2 = 0;
    TIMx->CCER = 0;
    // setup the counter, reload value and prescalar
    TIMx->CNT = 0;
    TIMx->PSC = ((SystemCoreClock / 2) /(CDC_TIMER_PERIOD * CDC_TIMER_HZ)) - 1;
    TIMx->ARR = CDC_TIMER_PERIOD - 1;
    // no output compare values
    TIMx->CCR1 = 0;
    TIMx->CCR2 = 0;
    TIMx->CCR3 = 0;
    TIMx->CCR4 = 0;
    // dma is not used
    TIMx->DCR = 0;
    TIMx->DMAR = 0;

    // generate update event to load registers
    TIMx->EGR = TIM_EGR_UG;

    // enable the interrupt - run this at low priority.
    enable_tim_interrupt(TIMx, 15, 0);
}

void cdc_timer_start(void)
{
    TIM_TypeDef* const TIMx = CDC_TIMER;

    // enable update interrupts
    TIMx->DIER |= TIM_DIER_UIE;
    // turn on the timer
    TIMx->CR1 |= TIM_CR1_CEN;
}

void TIM3_IRQHandler(void)
{
    TIM_TypeDef* const TIMx = CDC_TIMER;

    if ((TIMx->SR & TIM_SR_UIF) && (TIMx->DIER & TIM_DIER_UIE)) {
        TIMx->SR &= ~TIM_SR_UIF;
        cdc_timer_isr();
    }
}

#endif // USB_SERIAL

//-----------------------------------------------------------------------------
// On the G540 a charge pump signal (>= 10KHz square wave) is used as a keepalive.
// Use PWM mode (TIM4/CH2) to generate a 12 kHz square wave
// There is no interrupt used.

#define G540_TIMER TIM4
#define G540_TIMER_HZ 12000
#define G540_TIMER_PERIOD 200

static void g540_timer_init(void)
{
    TIM_TypeDef* const TIMx = G540_TIMER;

    // enable the peripheral clock
    enable_tim_clock(TIMx);

    // up counter, edge aligned mode, arr is buffered
    TIMx->CR1 = TIM_CR1_ARPE;
    TIMx->CR2 = 0;
    // slave mode control register (not used)
    TIMx->SMCR = 0;
    // disable and clear interrupts
    TIMx->DIER = 0;
    TIMx->SR = 0;
    // setup the output control mode
    TIMx->CCMR1 = (6 << 12 /*OC2 = PWM mode 1*/) | (1 << 11 /*OC2 preload enable*/);
    TIMx->CCMR2 = 0;
    // disable any output (for now)
    TIMx->CCER = 0;
    // setup the counter, reload value and prescalar
    TIMx->CNT = 0;
    TIMx->PSC = ((SystemCoreClock / 2) /(G540_TIMER_PERIOD * G540_TIMER_HZ)) - 1;
    TIMx->ARR = G540_TIMER_PERIOD - 1;
    // setup the output compare values (channel2 only)
    TIMx->CCR1 = 0;
    TIMx->CCR2 = G540_TIMER_PERIOD / 2;
    TIMx->CCR3 = 0;
    TIMx->CCR4 = 0;
    // dma is not used
    TIMx->DCR = 0;
    TIMx->DMAR = 0;

    // generate update event to load registers
    TIMx->EGR = TIM_EGR_UG;
}

void g540_timer_start(void)
{
    TIM_TypeDef* const TIMx = G540_TIMER;
    // turn on the channel2 output compare
    TIMx->CCER |= TIM_CCER_CC2E;
    // turn on the timer
    TIMx->CR1 |= TIM_CR1_CEN;
}

void g540_timer_stop(void)
{
    TIM_TypeDef* const TIMx = G540_TIMER;
    // turn off the timer
    TIMx->CR1 &= ~TIM_CR1_CEN;
    // turn off the channel2 output compare
    TIMx->CCER &= ~TIM_CCER_CC2E;
}

//-----------------------------------------------------------------------------

void timers_init(void)
{
    step_timer_init();
#ifdef USB_SERIAL
    cdc_timer_init();
#endif
    g540_timer_init();

    g540_timer_start();
}

//-----------------------------------------------------------------------------
